# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import base64
import logging
import pprint
import struct
import uuid
import yaml


EVENT_SEPARATOR = '\n---\n'


def YamlObjectConstructor(loader, unused_tag_suffix, node):
  """A custom YAML constructor to construct objects as lists or dicts."""
  if isinstance(node, yaml.SequenceNode):
    return loader.construct_yaml_seq(node)
  elif isinstance(node, yaml.MappingNode):
    return loader.construct_yaml_map(node)
  else:
    return None


# Custom YAML loader to accept more datatypes than SafeLoader, e.g. treating
# tuples as lists, unicodes/names as strings, and objects as lists/dicts.
CustomLoader = yaml.SafeLoader
CustomLoader.add_constructor('tag:yaml.org,2002:python/tuple',
                             CustomLoader.construct_yaml_seq)
CustomLoader.add_constructor('tag:yaml.org,2002:python/unicode',
                             CustomLoader.construct_yaml_str)
CustomLoader.add_multi_constructor(
    'tag:yaml.org,2002:python/name',
    lambda loader, _, node: CustomLoader.construct_yaml_str(loader, node))
CustomLoader.add_multi_constructor('tag:yaml.org,2002:python/object',
                                   YamlObjectConstructor)


class EventBlob(object):
  """A structure to wrap the information returned from event log watcher.

  Properties:
    metadata: A dict to keep the metadata.
    chunk: A byte-list to store the orignal event data.
  """

  def __init__(self, metadata, chunk):
    self.metadata = metadata
    self.chunk = chunk


class EventStream(list):
  """Event Stream Structure.

  An EventStream is a list to store multiple non-preamble events, which share
  the same preamble event.

  Properties:
    metadata: A dict to keep the metadata.
    preamble: The dict of the preamble event.
  """

  def __init__(self, metadata):
    super(EventStream, self).__init__()
    self.metadata = metadata
    self.preamble = None

  def __nonzero__(self):
    # True on a valid preamble or a non-empty list.
    return (self.preamble is not None) or (len(self) > 0)


def GenerateEventStreamsFromYaml(metadata, yaml_str):
  """Generates EventStreams from multiple yaml-formatted events with delimiters.

  Args:
    metadata: A dict to keep the metadata.
    yaml_str: The string contains multiple yaml-formatted events.

  Yields:
    EventStream objects.
  """
  first = True
  stream = EventStream(metadata)
  for event_str in yaml_str.split(EVENT_SEPARATOR):
    event = None
    try:
      event = yaml.load(event_str, Loader=CustomLoader)
    except yaml.YAMLError:
      logging.exception('Error on parsing the yaml string %r', event_str)
    if not event:
      continue
    if 'EVENT' not in event:
      logging.warn('The event dict is invalid, no EVENT tag:\n%s.',
                   pprint.pformat(event))
      continue
    if event['EVENT'] == 'preamble':
      # Yeild the stream it just created when it meets a new preamble,
      # except the case of the first one.
      if not first:
        yield stream
        stream = EventStream(metadata)
      stream.preamble = event
    else:
      stream.append(event)
    first = False
  yield stream


class EventPacket(object):
  """Event Packet Structure.

  An EventPacket is a non-preamble event combined with its preamble. It is
  used as an argument to pass to the exporters.

  Properties:
    metadata: A dict to keep the metadata.
    preamble: The dict of the preamble event.
    event: The dict of the non-preamble event.
    _event_id: The event_id string.
  """

  def __init__(self, metadata, preamble, event):
    self.metadata = metadata
    self.preamble = preamble
    self.event = event
    self._event_id = None

  @staticmethod
  def FlattenAttr(attr):
    """Generator of flattened attributes.

    Args:
      attr: The attr dict/list which may contains multi-level dicts/lists.

    Yields:
      A tuple (path_str, leaf_value).
    """
    def _FlattenAttr(attr):
      if isinstance(attr, dict):
        for key, val in attr.iteritems():
          for path, leaf in _FlattenAttr(val):
            yield [str(key)] + path, leaf
      elif isinstance(attr, list):
        for index, val in enumerate(attr):
          for path, leaf in _FlattenAttr(val):
            yield [str(index)] + path, leaf
      else:
        # The leaf node.
        yield [], attr

    # Join the path list using '.'.
    return (('.'.join(k), v) for k, v in _FlattenAttr(attr))

  def GetEventId(self):
    """Generates the unique ID for an event, the base64 of {reimage_id}{SEQ}."""
    if not self._event_id:
      reimage_id = (self.preamble.get('reimage_id') or
                    self.preamble.get('image_id'))
      reimage_id_bytes = uuid.UUID(reimage_id).bytes
      seq_bytes = struct.pack('>L', int(self.event.get('SEQ')))
      self._event_id = ''.join([base64.urlsafe_b64encode(s).rstrip('=')
                                for s in [reimage_id_bytes, seq_bytes]])
    return self._event_id

  def FindAttrContainingKey(self, key):
    """Finds the attr in the event that contains the given key.

    Args:
      key: A string of key.

    Returns:
      The dict inside the event that contains the given key.
    """
    def _FindContainingDictForKey(deep_dict, key):
      if isinstance(deep_dict, dict):
        if key in deep_dict.iterkeys():
          # Found, return its parent.
          return deep_dict
        else:
          # Try its children.
          for val in deep_dict.itervalues():
            result = _FindContainingDictForKey(val, key)
            if result:
              return result
      elif isinstance(deep_dict, list):
        # Try its children.
        for val in deep_dict:
          result = _FindContainingDictForKey(val, key)
          if result:
            return result
      # Not found.
      return None

    return _FindContainingDictForKey(self.event, key)
